package kernel.stl;

import kernel.stl.exception.InvalidScaleException;
import kernel.stl.exception.InvalidZIndexException;
import kernel.stl.exception.UndefinedPointException;

/**A classe Facet representa a faceta (ou face) de um arquivo STL.
 * Uma faceta é um triangulo e, por tanto, é definido por três pontos: 
 * Vertex1, Vertex2 e Vertex3. Além disso, ela possui um vetor 
 * normal que determina o lado para o qual a faceta esta voltada.
 * @author George
 */
public class Facet {

    //Vértices que definem o triângulo
    private PointXYZ vertex1; 
    private PointXYZ vertex2;
    private PointXYZ vertex3;
    
    //Vetor normal
    private Vector normal;

    /**Construtor da faceta.
     * @param vertex1 Vértice 1 do triângulo
     * @param vertex2 Vértice 2 do triângulo
     * @param vertex3 Vértice 3 do triângulo
     * @param normal Vetor normal da faceta
     */
    public Facet(PointXYZ vertex1, PointXYZ vertex2, PointXYZ vertex3, 
            Vector normal) {
        this.vertex1 = vertex1;
        this.vertex2 = vertex2;
        this.vertex3 = vertex3;

        this.normal = normal;
    }
    
    /**Avalia se o plano determinado pela função f(x, y) = zIndex intercepta um 
     * segmento de reta.
     * @param line Linha avaliada
     * @param zIndex valor que determina o plano
     * @return true caso a linha intercepte o plano ou false no caso contrário
     */
    private boolean zPlanInterceptsSegmentLine(LineSegment line, double zIndex) {
        if ((line.getMaxZ() >= zIndex) && (line.getMinZ() <= zIndex)) {
            return true;
        } else {
            return false;
        }
    }
    
    /**Cria uma string com os três vértices que representam a faceta. 
     * @return String gerada
     */
    public String asString() {
        return "Faceta: [" + vertex1.asString() + ", "  + vertex2.asString() +
                ", " + vertex3.asString() + "] ";
    }
    
    private PointXYZ createNewVertexWithModifiedScale(PointXYZ vertex, 
            double scale) throws InvalidScaleException {
        if (scale <= 0) 
            throw new InvalidScaleException();        
        
        double x = vertex.getX() * scale;
        double y = vertex.getY() * scale;
        double z = vertex.getZ() * scale;
        
        return new PointXYZ(x, y, z);
    }
    
    /**Altera o tamanho de acordo com a nova escala.
     * Todos os compenentes x, y e z dos vértices são multiplicados pela nova 
     * escala e por tanto o posicionamento da faceta também é alterada. 
     * @param scale Nova escala da faceta.
     * @throws kernel.stl.exception.InvalidScaleException
     */
    public void changeScale(double scale) throws InvalidScaleException {
        this.vertex1 = createNewVertexWithModifiedScale(this.vertex1, scale);
        this.vertex2 = createNewVertexWithModifiedScale(this.vertex2, scale);
        this.vertex3 = createNewVertexWithModifiedScale(this.vertex3, scale);        
    }
    
    /**Gera uma lista de segmentos de reta obtidos através da interseção do 
     * plano z com a faceta.
     * A lista criada pode não conter nenhum segmento de reta, caso o plano 
     * f(x, y) = zIndex não interce a faceta; retornar um segmento de reta caso 
     * a interseção seja definida por uma reta; retornar três segmentos de reta
     * caso a faceta esteja no plano f(x, y) = zIndex 
     * @param zIndex indice z utilizado para gerar o plano f(x, y) = z 
     * @return Lista de segmentos de reta determinados pela interseção entre o z 
     * e a faceta
     * @throws kernel.stl.exception.InvalidZIndexException ocorre caso um índice
     * z inválido seja passado.
     */    
    public LineSegmentList getLineSegmentListAt(double zIndex) throws 
            InvalidZIndexException, UndefinedPointException {
        LineSegmentList list = getLineSegmentListAtZIndex(zIndex);                
        return list;        
    }
    
    private LineSegmentList getLineSegmentListAtZIndex(double zIndex) throws 
            InvalidZIndexException, UndefinedPointException {
        LineSegmentList list = new LineSegmentList();
        //A interseção entre a faceta e o plano pode resultar em:
        //1 - Nenhuma reta, caso o plano z não intercete a faceta
        //2 - Uma reta, caso o plano da faceta não seja conicidente com o plano z
        //3 - Três retas, caso o plano da faceta esteja seja conicidente com o plano z
        
        
        if ((this.getMaxZ() < zIndex) || (this.getMinZ() > zIndex)) {
            //Neste caso, o plano z não intercepta a faceta. A lista vazia é retornada.
            return list;            
        } else { //se chegou ate aqui, o plano f(x,y) = zIndex intercepta a faceta
            
            if (this.getMaxZ() == this.getMinZ()) {
                //significa que o plano da faceta é coincidente com o plano f(x,y)
                list.addLineSegment(this.getLineSegment12(), 0);
                list.addLineSegment(this.getLineSegment13(), 0);
                list.addLineSegment(this.getLineSegment23(), 0);
                return list;
            } else if (this.getLineSegment12().getPoint1().getZ() ==
                    this.getLineSegment12().getPoint2().getZ() &&
                    this.getLineSegment12().getPoint2().getZ() == zIndex) {
                //caso o segmento12 da faceta esteja contida no plano f(x,y)
                list.addLineSegment(this.getLineSegment12(), 0);
                return list;
            } else if (this.getLineSegment13().getPoint1().getZ() ==
                    this.getLineSegment13().getPoint2().getZ() &&
                    this.getLineSegment13().getPoint2().getZ() == zIndex) {
                //caso o segmento13 da faceta esteja no plano f(x,y)
                list.addLineSegment(this.getLineSegment13(), 0);
                return list;
            } else if (this.getLineSegment23().getPoint1().getZ() ==
                    this.getLineSegment23().getPoint2().getZ() &&
                    this.getLineSegment23().getPoint2().getZ() == zIndex) {
                //caso o segmento23 da faceta esteja no plano f(x,y)
                list.addLineSegment(this.getLineSegment23(), 0);
                return list;
            }
            
            //neste ponto, sabe-se que nunehum dos três segmentos de reta da faceta
            //está contido no plano f(x,y)=z. Por outro lado,  sabe-se que este plano
            //intercepta dois segmentos de reta, mas não intercepta o terceiro. O código
            //a seguir avalia quem são estas duas retas e armazena-as em line1 e line2.
            LineSegment line1 = null;
            LineSegment line2 = null;

            if (!this.zPlanInterceptsSegmentLine(this.getLineSegment12(), zIndex)) {
                line1 = this.getLineSegment13();
                line2 = this.getLineSegment23();
            } else if (!this.zPlanInterceptsSegmentLine(this.getLineSegment13(), zIndex)) {
                line1 = this.getLineSegment12();
                line2 = this.getLineSegment23();
            } else if (!this.zPlanInterceptsSegmentLine(this.getLineSegment23(), zIndex)) {
                line1 = this.getLineSegment12();
                line2 = this.getLineSegment13();
            }
            
            //aqui os dois segmentos de reta que interceptam o plano já foram encontrados e é
            //criado um novo segmento de reta determinado pelos pontos de interseção.
            PointXYZ point1 = line1.getPointAt(zIndex);
            PointXYZ point2 = line2.getPointAt(zIndex);

            list.addLineSegment(new LineSegment(point1, point2), 0);            

            return list;
        }
    }

    /**Move a faceta ao longo do eixo x.
     * A movimentação é feita com base em um offset. A direção do movimento é 
     * determinada pelo sinal do offset.
     * @param offset tamanho do deslocamento da faceta. 
     */
    public void moveX(double offset) {
        this.vertex1.moveX(offset);
        this.vertex2.moveX(offset);
        this.vertex3.moveX(offset);
    }
    
    /**Move a faceta ao longo do eixo y.
     * A movimentação é feita com base em um offset. A direção do movimento é 
     * determinada pelo sinal do offset.
     * @param offset tamanho do deslocamento da faceta. 
     */
    public void moveY(double offset) {
        this.vertex1.moveY(offset);
        this.vertex2.moveY(offset);
        this.vertex3.moveY(offset);
    }
    
    /**Move a faceta ao longo do eixo z.
     * A movimentação é feita com base em um offset. A direção do movimento é 
     * determinada pelo sinal do offset.
     * @param offset tamanho do deslocamento da faceta 
     */
    public void moveZ(double offset) {
        this.vertex1.moveZ(offset);
        this.vertex2.moveZ(offset);
        this.vertex3.moveZ(offset);
    }
    
    public void exchangeXYAxis() {
        this.vertex1.exchangeXYAxis();
        this.vertex2.exchangeXYAxis();
        this.vertex3.exchangeXYAxis();
    }
    
    public void exchangeXZAxis() {
        this.vertex1.exchangeXZAxis();
        this.vertex2.exchangeXZAxis();
        this.vertex3.exchangeXZAxis();
    }
        
    public void exchangeYZAxis() {
        this.vertex1.exchangeYZAxis();
        this.vertex2.exchangeYZAxis();
        this.vertex3.exchangeYZAxis();
    }

    /**Retorna o segmento de reta formado pelo vértice 1 e pelo vértice 2.
     * @return um dos segmentos de reta da faceta.
     */
    public LineSegment getLineSegment12() {
        return new LineSegment(this.vertex1, this.vertex2);
    }

    /**Retorna o segmento de reta formado pelo vértice 1 e pelo vértice 3.
     * @return um dos segmentos de reta da faceta.
     */
    public LineSegment getLineSegment13() {
        return new LineSegment(this.vertex1, this.vertex3);
    }
    /**Retorna o segmento de reta formado pelo vértices 1 e pelo vértice 3. 
     * @return um dos segmentos da faceta.
     */
    public LineSegment getLineSegment23() {
        return new LineSegment(this.vertex2, this.vertex3);
    }

    /**Avalia se a faceta possui um segmento de reta.
     * @param line Segmento de reta a ser localizado na faceta.
     * @return true caso o segmento passado esteja presenta na faceta e false 
     * no caso contrário.
     */
    public boolean hasLineSegment(LineSegment line) {
        if (line.equals(this.getLineSegment12(), STLConstants.TOLERANCE)) {
            return true;
        } else if (line.equals(this.getLineSegment13(), STLConstants.TOLERANCE)) {
            return true;
        } else if (line.equals(this.getLineSegment23(), STLConstants.TOLERANCE)) {
            return true;
        } else {
            return false;
        }
    }

    /**Avalia se a faceta está conetada diretamente a outra.
     * Para estar conectada diretamente, é necessário que um dos segmentos de 
     * reta desta faceta seja igual a um dos segmentos de reta da outra faceta.
     * @param otherFacet outra faceta a ser avaliada.
     * @return true caso a outra facet estiver diretamente conectada e false no 
     * caso contrário.
     */
    public boolean isConnectedTo(Facet otherFacet) {
        if (this.hasLineSegment(otherFacet.getLineSegment12())) {
            return true;
        } else if (this.hasLineSegment(otherFacet.getLineSegment13())) {
            return true;
        } else if (this.hasLineSegment(otherFacet.getLineSegment23())) {
            return true;
        } else {
            return false;
        }
    }

    /**Verifica se a faceta possui o vértice passado por parâmetro.
     * Como nenhuma tolerância é especificada neste método, é assumido o valor
     * zero para esta variável.
     * @param vertex vértice a ser localizado.
     * @return True caso o vértice seja econtrado ou false no caso contrário.
     */
    public boolean hasVertex(PointXYZ vertex) {
        return hasVertex(vertex);
    }
    
    /**Verifica se a faceta possui o vértice passado por parâmetro.
     * Para a busca do vértice, será especificada uma tolerância. Esta
     * tolerância representa a distância máxima entre dois pontos para
     * considerá-los iguais.
     * @param vertex vértice a ser localizado na faceta.
     * @param tolerance distância máxima entre dois pontos para considerá-los 
     * iguais.
     * @return true caso o vértice seja encontrado na faceta.
     */
    public boolean hasVertex(PointXYZ vertex, double tolerance) {
        if (vertex.equals(vertex1, tolerance) || 
                vertex.equals(vertex2, tolerance) ||
                vertex.equals(vertex2, tolerance)) {
            return true;
        } else {
            return false;
        }
    }
    
    /**Verifica compara a faceta atual com a faceta passada por parâmetro. 
     * Este método compara os três pontos de uma faceta, de acordo com a
     * tolerância especificada. Esta tolerância indica a distância máxima entre
     * dois pontos para considerá-los iguais. É importante observar que duas 
     * facetas são consideradas iguais quando uma possui os mesmos três pontos 
     * da outra. Por tanto, não é necessário que a ordem dos pontos seja a mesma. 
     * @param otherFacet faceta a ser comparada.
     * @param tolerance diferença máxima entre os pontos x, y e z comparados.
     * @return
     */    
    public boolean equals(Facet otherFacet, double tolerance) {
        if (otherFacet.hasVertex(vertex1, tolerance) && 
                otherFacet.hasVertex(vertex2, tolerance) &&
                otherFacet.hasVertex(vertex3, tolerance)) {
            return true;
        } else {
            return false;
        }
    }

    /**Retorna o primeiro vértice da faceta. 
     * @return primeiro vértice da faceta.
     */
    public PointXYZ getVertex1() {
        return this.vertex1;
    }

    /**Retona o segundo vértice da faceta.
     * @return segundo vértice da faceta.
     */  
    public PointXYZ getVertex2() {
        return this.vertex2;
    }

    /**Retorna o terceiro vértice da faceta.
     * @return terceiro vértice da faceta.
     */
    public PointXYZ getVertex3() {
        return this.vertex3;
    }

    /**Retorna o vetor normal da faceta.
     * @return vetor normal da faceta.
     */
    public Vector getNormal() {
        return this.normal;
    }

    /**Retorna o valor máximo do eixo X da faceta.
     * @return valor máximo do eixo X da faceta.
     */
    public double getMaxX() {
        double maximum = this.getVertex1().getX();
        if (this.getVertex2().getX() > maximum) {
            maximum = this.getVertex2().getX();
        }
        if (this.getVertex3().getX() > maximum) {
            maximum = this.getVertex3().getX();
        }
        return maximum;
    }

    /**Retorna o valor mínimo do eixo X da faceta.
     * @return valor mínimo do eixo X da faceta.
     */
    public double getMinX() {
        double minimum = this.getVertex1().getX();
        if (this.getVertex2().getX() < minimum) {
            minimum = this.getVertex2().getX();
        }
        if (this.getVertex3().getX() < minimum) {
            minimum = this.getVertex3().getX();
        }
        return minimum;
    }

    /**Retorna o valor máximo do eixo Y da faceta.
     * @return valor máximo do eixo Y da faceta.
     */
    public double getMaxY() {
        double maximum = this.getVertex1().getY();
        if (this.getVertex2().getY() > maximum) {
            maximum = this.getVertex2().getY();
        }
        if (this.getVertex3().getY() > maximum) {
            maximum = this.getVertex3().getY();
        }
        return maximum;
    }

    /**Retorna o valor mínimo do eixo Y da faceta.
     * @return valor mínimo do eixo Y da faceta.
     */
    public double getMinY() {
        double minimum = this.getVertex1().getY();
        if (this.getVertex2().getY() < minimum) {
            minimum = this.getVertex2().getY();
        }
        if (this.getVertex3().getY() < minimum) {
            minimum = this.getVertex3().getY();
        }
        return minimum;
    }

    /**Retorna o valor máximo do eixo Z da faceta.
     * @return valor máximo do eixo Z da faceta.
     */
    public double getMaxZ() {
        double maximum = this.getVertex1().getZ();
        if (this.getVertex2().getZ() > maximum) {
            maximum = this.getVertex2().getZ();
        }
        if (this.getVertex3().getZ() > maximum) {
            maximum = this.getVertex3().getZ();
        }
        return maximum;
    }

    /**Retorna o valor mínimo do eixo Z da faceta.
     * @return valor mínimo do eixo Z da faceta.
     */
    public double getMinZ() {
        double minimum = this.getVertex1().getZ();
        if (this.getVertex2().getZ() < minimum) {
            minimum = this.getVertex2().getZ();
        }
        if (this.getVertex3().getZ() < minimum) {
            minimum = this.getVertex3().getZ();
        }
        return minimum;
    }
}
